package xcrypt

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/hex"
	"errors"
	"golang.org/x/crypto/bcrypt"
	"strings"
)

const (
	CBC = "CBC"
	ECB = "ECB"
)

// EncryptPassword 加密密码
func EncryptPassword(password string) (string, error) {
	genBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	return string(genBytes), err
}

// ComparePassword 比较密码
func ComparePassword(hashedPassword, password string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
	return err == nil
}

func EncryptAESWithCBC(data, key []byte) ([]byte, error) {
	return EncryptAES(data, key, CBC)
}

func DecryptAESWithCBC(data, key []byte) ([]byte, error) {
	return DecryptAES(data, key, CBC)

}

func EncryptAESWithECB(data, key string) (string, error) {
	encrypted, err := EncryptAES([]byte(data), []byte(key), ECB)
	if err != nil {
		return "", err
	}
	// 16进制转化
	cryptStr := strings.ToUpper(hex.EncodeToString(encrypted))

	return cryptStr, nil
}

func DecryptAESWithECB(data, key string) (string, error) {
	// 16进制转化
	dataByte, err := hex.DecodeString(strings.ToLower(data))
	if err != nil {
		panic(err)
	}
	decrypted, err := DecryptAES(dataByte, []byte(key), ECB)
	if err != nil {
		return "", err
	}
	return string(decrypted), nil
}

func EncryptAES(data, key []byte, mode string) ([]byte, error) {
	//创建加密实例
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	//判断加密快的大小
	blockSize := block.BlockSize()
	//填充
	encryptBytes := pkcs7Padding(data, blockSize)
	//初始化加密数据接收切片
	encrypted := make([]byte, len(encryptBytes))

	var blockMode cipher.BlockMode
	//使用cbc加密模式
	switch mode {
	case ECB:
		blockMode = NewECBEncrypt(block)
	case CBC:
		blockMode = cipher.NewCBCEncrypter(block, key[:blockSize])
	}
	if blockMode == nil {
		return nil, errors.New("mode error")
	}
	//执行加密
	blockMode.CryptBlocks(encrypted, encryptBytes)
	// 16进制转化
	return encrypted, nil
}

func DecryptAES(data []byte, key []byte, mode string) ([]byte, error) {

	//创建实例
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	var blockMode cipher.BlockMode
	switch mode {
	case ECB:
		blockMode = NewECBDecrypt(block)
	case CBC:
		blockSize := block.BlockSize()
		blockMode = cipher.NewCBCDecrypter(block, key[:blockSize])
	}
	if blockMode == nil {
		return nil, errors.New("mode error")
	}
	//初始化解密数据接收切片
	decrypted := make([]byte, len(data))
	//执行解密
	blockMode.CryptBlocks(decrypted, data)
	//去除填充
	decrypted, err = pkcs7UnPadding(decrypted)
	if err != nil {
		return nil, err
	}
	return decrypted, nil
}

// pkcs7Padding 填充
func pkcs7Padding(data []byte, blockSize int) []byte {
	//判断缺少几位长度。最少1，最多 blockSize
	padding := blockSize - len(data)%blockSize
	//补足位数。把切片[]byte{byte(padding)}复制padding个
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padText...)
}

// pkcs7UnPadding 填充的反向操作
func pkcs7UnPadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, errors.New("data is empty")
	}
	//获取填充的个数
	unPadding := int(data[length-1])
	return data[:(length - unPadding)], nil
}

type ecb struct {
	b         cipher.Block
	blockSize int
}

func newECB(b cipher.Block) *ecb {
	return &ecb{
		b:         b,
		blockSize: b.BlockSize(),
	}
}

type ebbEncrypt ecb

func NewECBEncrypt(b cipher.Block) cipher.BlockMode {
	return (*ebbEncrypt)(newECB(b))
}

func (x *ebbEncrypt) BlockSize() int { return x.blockSize }
func (x *ebbEncrypt) CryptBlocks(dst, src []byte) {
	if len(src)%x.blockSize != 0 {
		panic("crypto/cipher: input not full blocks")
	}
	if len(dst) < len(src) {
		panic("crypto/cipher: output smaller than input")
	}
	for len(src) > 0 {
		x.b.Encrypt(dst, src[:x.blockSize])
		src = src[x.blockSize:]
		dst = dst[x.blockSize:]
	}
}

type ecbDecrypt ecb

// NewECBDecrypt returns a BlockMode which decrypts in electronic code book
// mode, using the given Block.
func NewECBDecrypt(b cipher.Block) cipher.BlockMode {
	return (*ecbDecrypt)(newECB(b))
}
func (x *ecbDecrypt) BlockSize() int { return x.blockSize }
func (x *ecbDecrypt) CryptBlocks(dst, src []byte) {
	if len(src)%x.blockSize != 0 {
		panic("crypto/cipher: input not full blocks")
	}
	if len(dst) < len(src) {
		panic("crypto/cipher: output smaller than input")
	}
	for len(src) > 0 {
		x.b.Decrypt(dst, src[:x.blockSize])
		src = src[x.blockSize:]
		dst = dst[x.blockSize:]
	}
}
